import Store from '@/utils/store'
import output from '@/utils/output'
import { nanoid } from 'nanoid'
import { Packet } from './packet'
import { ConnectLogger } from './log'
import { EventEmitter } from 'events'
import { InitiateConnectAction, ReceiveConnectAction } from './actions/connect'
import {
  decryptWithAESGCM,
  encryptWithAESGCM,
  generateKeyPairWithEC,
  generateKeyPairWithRSA
} from '@/utils/crypto'
import type { AddressInfo, Socket } from 'net'
import type { Encoding } from '@/types/common'
import type { ParamHeader, HeaderType } from './packet'
import type { ConnectAction, ConnectActionGroup } from './action'
import { formatAddressInfo } from '@/utils/format'

export class Connect extends EventEmitter {
  static readonly DEFAULT_ADDRESS_INFO: AddressInfo = {
    family: 'IPv4',
    address: '127.0.0.1',
    port: 0
  }

  /**
   * 连接对象唯一标识
   */
  readonly id: string

  readonly logger: ConnectLogger

  readonly socket: Socket

  protected _isConnected = false

  get isConnected() {
    return this._isConnected
  }

  constructor(socket: Socket, logDirectory: string) {
    super()
    this.id = nanoid()
    this.socket = socket
    this.addressInfo = this.getAddress()
    this.remoteAddressInfo = this.getRemoteAddressInfo()
    this.logger = new ConnectLogger(logDirectory, this.id)

    this.bindEvent()
    this.generateKey()

    // 添加默认的 Action
    this.addAction(new InitiateConnectAction(this))
    this.addAction(new ReceiveConnectAction(this))
  }

  /**
   * 当前连接仓库
   */
  readonly store = new Store()

  /**
   * 连接动作
   */
  readonly actions: (ConnectAction | ConnectActionGroup)[] = []

  addAction(action: ConnectAction | ConnectActionGroup) {
    this.actions.push(action)
  }

  readonly addressInfo: AddressInfo

  readonly remoteAddressInfo: AddressInfo

  currentActionName: string | null = null

  async runAction(name: string): Promise<string | undefined> {
    const index = this.actions.findIndex((action) => action.name === name)
    if (index === -1) {
      const err = new Error(`Action ${name} not found`)
      this.logger.error(err, name)
      throw err
    }
    this.currentActionName = name
    this.logger.info(`Run action: ${name}`)
    return await this.actions[index].runPromise()
  }

  getAddress() {
    return formatAddressInfo(this.socket.address(), Connect.DEFAULT_ADDRESS_INFO)
  }

  getRemoteAddressInfo(): AddressInfo {
    return formatAddressInfo(
      {
        address: this.socket.remoteAddress,
        family: this.socket.remoteFamily,
        port: this.socket.remotePort
      },
      Connect.DEFAULT_ADDRESS_INFO
    )
  }

  /**
   * 是否过滤 pong 消息包
   */
  filterPong = true

  /**
   * 是否输出错误消息包的内容
   */
  outputError = false

  /**
   * 是否加密消息包
   */
  protected encryptPacket = false

  /**
   * 数据包加密密钥
   */
  protected encryptPacketKey: Buffer | null = null

  /**
   * 开启加密数据包
   */
  openEncryptPacket(key: Buffer) {
    this.encryptPacketKey = key
    this.encryptPacket = true
  }
  /**
   * 关闭加密数据包
   */
  closeEncryptPacket() {
    this.encryptPacketKey = null
    this.encryptPacket = false
  }

  on<K extends keyof ConnectEvents>(event: K, listener: ConnectEvents[K]) {
    return super.on(event, listener)
  }

  once<K extends keyof ConnectEvents>(event: K, listener: ConnectEvents[K]) {
    return super.once(event, listener)
  }

  off<K extends keyof ConnectEvents>(event: K, listener: ConnectEvents[K]) {
    return super.off(event, listener)
  }

  emit<K extends keyof ConnectEvents>(event: K, ...args: Parameters<ConnectEvents[K]>) {
    return super.emit(event, ...args)
  }

  onReturnOff<K extends keyof ConnectEvents>(event: K, listener: ConnectEvents[K]) {
    this.on(event, listener)
    return () => this.off(event, listener)
  }

  onceReturnOff<K extends keyof ConnectEvents>(event: K, listener: ConnectEvents[K]) {
    this.once(event, listener)
    return () => this.off(event, listener)
  }

  /**
   * 发送消息包
   * @param packet 消息包
   */
  send(packet: Packet) {
    return new Promise<void>((resolve, reject) => {
      try {
        if (this.encryptPacket && packet.body) {
          if (!this.encryptPacketKey) {
            throw this.loggerError(new Error('Encrypt packet key not set'))
          }
          const encrypted = encryptWithAESGCM(packet.body, this.encryptPacketKey)
          packet.setBody(encrypted.encryptedData)
          packet.setHeader('tag', encrypted.tag)
          packet.setHeader('encrypt', true)
        }

        // 连接销毁阻止发送
        if (!this.isConnected || this.socket.destroyed) {
          this.logger.warn('Connection is closed, packet not sent')
          return
        }
        if (!this.socket.writable) {
          this.logger.error('Connection is not writable, packet not sent')
          return
        }
        this.socket.write(packet.encode(), (err) => {
          if (err) {
            this.logger.error(err)
            reject(err)
          } else {
            resolve()
          }
        })

        this.updateTimeout()
      } catch (error) {
        reject(error)
      }
    })
  }

  /**
   * 发送二进制数据
   * @param data 数据
   * @param header 消息头
   */
  sendBuffer(data: Buffer, header?: ParamHeader) {
    const packet = new Packet(data)

    if (header) {
      packet.setHeaders(header)
    }

    return this.send(packet)
  }

  /**
   * 发送文本
   * @param text 文本
   * @param header 消息头
   */
  sendText(text: string, header?: ParamHeader): Promise<void>
  /**
   * 发送文本
   * @param text 文本
   * @param header 消息头
   */
  sendText(text: string, charset?: Encoding): Promise<void>

  sendText(text: string, param?: ParamHeader | Encoding) {
    const packet = new Packet()

    if (typeof param === 'string') {
      packet.setBodyByText(text, param)
    } else if (param) {
      packet.setBodyByText(text, param.charset)
      packet.setHeaders(param)
    } else {
      packet.setBodyByText(text)
    }

    return this.send(packet)
  }

  /**
   * 发送 json 数据
   * @param json 数据
   * @param header 消息头
   */
  sendJson(json: any, header?: ParamHeader): Promise<void>
  /**
   * 发送 json 数据
   * @param json 数据
   * @param charset 编码
   */
  sendJson(json: any, charset?: Encoding): Promise<void>

  sendJson(json: any, param?: ParamHeader | Encoding) {
    const packet = new Packet()

    if (typeof param === 'string') {
      packet.setBodyByJson(json, param)
    } else if (param) {
      packet.setBodyByJson(json, param.charset)
      packet.setHeaders(param)
    } else {
      packet.setBodyByJson(json)
    }

    return this.send(packet)
  }

  /**
   * 发送 pong 消息包
   */
  sendPong() {
    this.send(Packet.createPontPacket())
  }

  private intervalSendPongTimer: NodeJS.Timeout | null = null

  /**
   * 开启间隔发送 pong 消息包
   * @param interval 间隔时间
   */
  startIntervalSendPong(interval = 3000, immediate = true) {
    this.stopIntervalSendPong()
    this.intervalSendPongTimer = setInterval(() => {
      this.sendPong()
    }, interval)
    if (immediate) this.sendPong()
  }

  /**
   * 停止间隔发送 pong 消息包
   */
  stopIntervalSendPong() {
    if (this.intervalSendPongTimer) {
      clearInterval(this.intervalSendPongTimer)
      this.intervalSendPongTimer = null
    }
  }

  /**
   * 发送ok消息
   */
  sendOk() {
    return this.send(Packet.createOkPacket())
  }

  /**
   * 发送错误消息
   */
  sendError(err?: string, header?: ParamHeader) {
    const p = Packet.createErrorPacket(err)
    if (header) p.setHeaders(header)
    return this.send(p)
  }

  /**
   * 发送致命错误消息
   */
  sendFatalError(err?: string, header?: ParamHeader) {
    const p = Packet.createFatalErrorPacket(err)
    if (header) p.setHeaders(header)
    return this.send(p)
  }

  sendTimeout() {
    return this.send(Packet.createTimeoutPacket())
  }

  /**
   * 结束连接
   */
  end() {
    this._isConnected = false
    return new Promise<void>((resolve) => {
      this.socket.end(() => {
        this.socket.destroy()
        resolve()
      })
    })
  }

  protected _timeout = 0
  protected _timeoutTimer: NodeJS.Timeout | null = null

  get timeout() {
    return this._timeout
  }

  private timeoutHandler() {
    console.log('connect timeout')
    this.emit('timeout')
    this.logger.warn('connect timeout')
    this.end()
  }

  /**
   * 开启超时
   */
  openTimeout(ms: number) {
    this._timeout = ms
    this._clearTimeout()
    this.updateTimeout()
  }

  /**
   * 关闭超时
   */
  closeTimeout() {
    this._timeout = 0
    this._clearTimeout()
  }

  /**
   * 更新超时时间
   */
  updateTimeout() {
    if (this._timeout === 0) return
    this._clearTimeout()
    this._timeoutTimer = setTimeout(() => this.timeoutHandler(), this._timeout)
  }

  /**
   * 清除超时时间
   */
  protected _clearTimeout() {
    if (this._timeoutTimer) {
      clearTimeout(this._timeoutTimer)
      this._timeoutTimer = null
    }
  }

  // 接收到数据和解析的信息
  private receivedData: Buffer = Buffer.alloc(0)
  private parsedHeaderLength: number | null = null
  private parsedHeader: HeaderType | null = null

  // 添加收到的数据
  private addReceivedData(data: Buffer) {
    this.receivedData = Buffer.concat([this.receivedData, data])
  }

  // 截取收到的数据
  private sliceReceivedData(length: number) {
    const data = this.receivedData.subarray(0, length)
    this.receivedData = this.receivedData.subarray(length)
    return data
  }

  // 获取数据包总长度
  private getPacketTotalLength() {
    if (this.parsedHeaderLength === null || this.parsedHeader === null) {
      throw new Error('not enough data')
    }
    return Packet.HEADER_BYTE_LENGTH + this.parsedHeaderLength + this.parsedHeader['content-length']
  }

  // 重置接收到数据和解析的信息
  private resetParsed() {
    this.parsedHeaderLength = null
    this.parsedHeader = null
  }

  // 清除接收到数据和解析的信息
  private clearParsed() {
    this.resetParsed()
    this.receivedData = Buffer.alloc(0)
  }

  // 处理接收到的数据
  private processReceivedData() {
    if (this.parsedHeaderLength === null) {
      this.parsedHeaderLength = Packet.parseHeaderLength(this.receivedData)
    }

    if (!this.parsedHeader && this.receivedData.length > this.parsedHeaderLength) {
      const header = Packet.decodeHeader(this.receivedData)
      this.emit('packet-header-parsed', header)
      this.parsedHeader = header
    }

    if (this.parsedHeader !== null) {
      const packetTotalLength = this.getPacketTotalLength()

      if (this.receivedData.length < packetTotalLength) {
        return // 数据长度不够
      }

      const data = this.sliceReceivedData(packetTotalLength)
      const packet = Packet.decode(data)

      if (!packet) {
        throw new Error('packet decode error')
      }

      this.emit('packet-parsed', packet)
      this.resetParsed()

      if (this.receivedData.length > 0) {
        // 有剩余数据，继续解析
        this.processReceivedData()
      }
    }
  }

  private socketDataEventHandler(data: Buffer) {
    this.updateTimeout()
    this.addReceivedData(data)
    this.processReceivedData()
  }

  private socketErrorEventHandler(err: Error) {
    this.logger.error(err, 'socket event')
    this.emit('error', err)
  }

  private socketCloseEventHandler(hadError: boolean) {
    if (hadError) {
      this.logger.info('close connect error')
    }
    this.clearParsed()
    this.closeTimeout()
    this.emit('end')
    this.logger.destroy()
    if (!this.isConnected) {
      this.logger.info('connect failure')
      this.emit('connect-failure')
    } else {
      this._isConnected = false
    }
  }

  private socketTimeoutEventHandler() {
    this.emit('timeout')
    this.logger.warn('connect timeout', 'socket event')
  }

  private packetParsedEventHandler(packet: Packet) {
    if (this.filterPong && packet.header.pong) return

    if (packet.header.encrypt && packet.body) {
      // 解密消息体
      try {
        if (!this.encryptPacketKey) {
          throw new Error('Encrypt packet key not set')
        }
        if (!packet.header.tag) {
          throw new Error('Encrypt packet no tag')
        }
        packet.setBody(decryptWithAESGCM(packet.body, this.encryptPacketKey, packet.header.tag))
      } catch (error) {
        throw this.loggerError(String(error))
      }
    }

    if (this.outputError && packet.isError() && packet.body) {
      output.error(packet.readErrorContent())
    }

    this.emit('packet', packet)
  }

  private socketConnectEventHandler() {
    this._isConnected = true
    this.logger.info('trigger connect event')
  }

  // 绑定事件
  private bindEvent() {
    this.socket.on('data', this.socketDataEventHandler.bind(this))
    this.socket.on('error', this.socketErrorEventHandler.bind(this))
    this.socket.on('close', this.socketCloseEventHandler.bind(this))
    this.socket.on('timeout', this.socketTimeoutEventHandler.bind(this))
    this.socket.on('connect', this.socketConnectEventHandler.bind(this))

    this.on('packet-parsed', this.packetParsedEventHandler.bind(this))
  }

  // 生成密钥
  private generateKey() {
    const keyWithEC = generateKeyPairWithEC()
    const keyWithRSA = generateKeyPairWithRSA()
    // const keyWithAESGCM = generateRandomKeyWithAESGCM()
    this.store.set('publicKeyWithEC', keyWithEC.publicKey)
    this.store.set('privateKeyWithEC', keyWithEC.privateKey)
    this.store.set('publicKeyWithRSA', keyWithRSA.publicKey)
    this.store.set('privateKeyWithRSA', keyWithRSA.privateKey)
    // this.store.set('keyWithAESGCM', keyWithAESGCM)
  }

  // TODO 去掉这个
  protected loggerError(err: Error | string) {
    this.logger.error(err)
    return err
  }

  /**
   * 异步监听一次收到的数据包
   */
  packetOnceAsync() {
    return new Promise<Packet>((resolve) => {
      this.once('packet', resolve)
    })
  }

  /**
   * 监听一次收到的数据包
   */
  packetOnce(listener: ConnectEvents['packet']) {
    this.once('packet', listener)
    return () => this.off('packet', listener)
  }

  /**
   * 监听收到的数据包
   */
  packetOn(listener: ConnectEvents['packet']) {
    this.on('packet', listener)
    return () => this.off('packet', listener)
  }

  /**
   * 等待缓冲区清空
   * @param max 最大等待时间，默认3秒
   */
  waitDrain(max = 3000, min = 0) {
    return new Promise<void>((resolve) => {
      if (this.socket.writableLength === 0 && !this.socket.writableNeedDrain) {
        if (min > 0) {
          setTimeout(() => resolve(), min)
        } else {
          return resolve()
        }
      }
      let isDrained = false
      const timer = setTimeout(() => {
        if (!isDrained) {
          this.socket.off('drain', cp)
          resolve()
        }
      }, max)
      const cp = () => {
        isDrained = true
        clearTimeout(timer)
        resolve()
      }
      this.socket.once('drain', cp)
    })
  }
}

export interface ConnectEvents {
  'packet-header-parsed': (header: HeaderType) => void
  'packet-parsed': (packet: Packet) => void
  packet: (packet: Packet) => void
  error: (err: Error) => void
  timeout: () => void
  end: () => void
  'connect-failure': () => void
}
